﻿// Copyright (c) 2008, Colin Burn
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 
// * Neither the name of WTCL nor the names of its contributors may be
// used to endorse or promote products derived from this software
// without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;

using WTCL.Charts.Axis.Data;
using WTCL.Charts.Axis.Scale;
using WTCL.Charts.Axis.Ticks;
using WTCL.Data.Series.ValueVsValueWithRange;
using WTCL.Data.Type;

namespace WTCL.Charts.ValueWithRangeChart
{
    public class ValueWithRangeChartCanvas<TXType, TXSpanType, TYType, TYSpanType> :
            ValueAxisCanvas<TXType, TXSpanType, TYType, TYSpanType, ValueWithRangeSeries<TXType, TXSpanType, TYType, TYSpanType>, ValueWithRangeData<TXType, TXSpanType, TYType, TYSpanType>, BaseValueWithRangeChart<TXType, TXSpanType, TYType, TYSpanType>, ValueWithRangeChartCanvas<TXType, TXSpanType, TYType, TYSpanType>>
    {
        private int cachedNoSeries;

        private List<Point>[] cachedPoints;
        private List<Point>[] cachedUpperPoints;
        private List<Point>[] cachedLowerPoints;

        private int[] cachedStartIndex;
        private int[] cachedEndIndex;

        private double chachedActualWidth;
        private double chachedActualHeight;

        private IDataType<TXType, TXSpanType>[] xStart;
        private IDataType<TXType, TXSpanType>[] xEnd;
        private IDataType<TYType, TYSpanType>[] yStart;
        private IDataType<TYType, TYSpanType>[] yEnd;

        internal override AxisScale<BaseValueWithRangeChart<TXType, TXSpanType, TYType, TYSpanType>, ValueAxisData<TXType, TXSpanType>, XAxisTick> CreateXScale(BaseValueWithRangeChart<TXType, TXSpanType, TYType, TYSpanType> chart)
        {
            return chart.CreateXScale();
        }

        internal override AxisScale<BaseValueWithRangeChart<TXType, TXSpanType, TYType, TYSpanType>, ValueAxisData<TYType, TYSpanType>, YAxisTick> CreateYScale(BaseValueWithRangeChart<TXType, TXSpanType, TYType, TYSpanType> chart)
        {
            return chart.CreateYScale();
        }

        protected override void OnRender(DrawingContext dc)
        {
            dc.PushClip(new RectangleGeometry(new Rect(0, 0, this.ActualWidth, this.ActualHeight)));

            if (this.ActualWidth > 0 && this.ActualHeight > 0)
            {
                if (this.chachedActualWidth != this.ActualWidth ||
                    this.chachedActualHeight != this.ActualHeight ||
                    this.cachedNoSeries != this.Chart.Series.Count)
                {
                    this.cachedNoSeries = this.Chart.Series.Count;

                    this.cachedPoints = new List<Point>[this.cachedNoSeries];
                    this.cachedUpperPoints = new List<Point>[this.cachedNoSeries];
                    this.cachedLowerPoints = new List<Point>[this.cachedNoSeries];

                    for (int i = 0; i < this.cachedNoSeries; ++i)
                    {
                        this.cachedPoints[i] = new List<Point>();
                        this.cachedUpperPoints[i] = new List<Point>();
                        this.cachedLowerPoints[i] = new List<Point>();
                    }

                    this.cachedStartIndex = new int[this.cachedNoSeries];
                    this.cachedEndIndex = new int[this.cachedNoSeries];

                    this.xStart = new IDataType<TXType, TXSpanType>[this.cachedNoSeries];
                    this.xEnd = new IDataType<TXType, TXSpanType>[this.cachedNoSeries];
                    this.yStart = new IDataType<TYType, TYSpanType>[this.cachedNoSeries];
                    this.yEnd = new IDataType<TYType, TYSpanType>[this.cachedNoSeries];

                    this.chachedActualWidth = this.ActualWidth;
                    this.chachedActualHeight = this.ActualHeight;
                }

                base.DrawBorder(dc, new Pen(new SolidColorBrush(Color.FromArgb(0xFF, 0x80, 0x80, 0x80)), 1));
                base.DrawGrid(dc, new Pen(new SolidColorBrush(Color.FromArgb(0xFF, 0x80, 0x80, 0x80)), 1));

                for (int i = 0; i < this.Chart.Series.Count; ++i)
                {
                    if (this.XAxisScale.AxisData[i] != null &&
                        this.YAxisScale.AxisData[i] != null)
                    {
                        if (this.XAxisScale.AxisData[i].ActualMinValue != null &&
                           this.XAxisScale.AxisData[i].ActualMaxValue != null &&
                           this.YAxisScale.AxisData[i].ActualMinValue != null &&
                           this.YAxisScale.AxisData[i].ActualMaxValue != null)
                        {
                            IDataType<TXType, TXSpanType> minXValue = this.XAxisScale.AxisData[i].ActualMinValue;
                            IDataType<TXType, TXSpanType> maxXValue = this.XAxisScale.AxisData[i].ActualMaxValue;

                            IDataType<TYType, TYSpanType> minYValue = this.YAxisScale.AxisData[i].ActualMinValue;
                            IDataType<TYType, TYSpanType> maxYValue = this.YAxisScale.AxisData[i].ActualMaxValue;

                            if (this.xStart[i] == null || !this.xStart[i].Equals(minXValue) ||
                                this.xEnd[i] == null || !this.xEnd[i].Equals(maxXValue) ||
                                this.yStart[i] == null || !this.yStart[i].Equals(minYValue) ||
                                this.yEnd[i] == null || !this.yEnd[i].Equals(maxYValue))
                            {
                                this.cachedPoints[i].Clear();
                                this.cachedUpperPoints[i].Clear();
                                this.cachedLowerPoints[i].Clear();

                                this.xStart[i] = minXValue;
                                this.xEnd[i] = maxXValue;
                                this.yStart[i] = minYValue;
                                this.yEnd[i] = maxYValue;
                            }

                            this.cachedStartIndex[i] = this.Chart.Series[i].IndexBefore(minXValue);
                            this.cachedEndIndex[i] = this.Chart.Series[i].IndexAfter(maxXValue);

                            if (this.cachedStartIndex[i] < 0)
                            {
                                this.cachedStartIndex[i] = 0;
                            }

                            if (this.cachedEndIndex[i] > this.Chart.Series[i].Samples.Count - 1)
                            {
                                this.cachedEndIndex[i] = this.Chart.Series[i].Samples.Count - 1;
                            }

                            for (int j = this.cachedPoints[i].Count + this.cachedStartIndex[i];
                                j < this.Chart.Series[i].Samples.Count && j < this.cachedEndIndex[i];
                                ++j)
                            {
                                IDataType<TXType, TXSpanType> xValue = this.Chart.Series[i].Samples[j].X;
                                IDataType<TYType, TYSpanType> yValue = this.Chart.Series[i].Samples[j].Y;
                                IDataType<TYType, TYSpanType> yUpperValue = this.Chart.Series[i].Samples[j].YUpper;
                                IDataType<TYType, TYSpanType> yLowerValue = this.Chart.Series[i].Samples[j].YLower;

                                double dx = this.ActualWidth * xValue.GetRatio(minXValue, maxXValue);
                                double dy = this.ActualHeight - this.ActualHeight * yValue.GetRatio(minYValue, maxYValue);
                                double dyUpper = this.ActualHeight - this.ActualHeight * yUpperValue.GetRatio(minYValue, maxYValue);
                                double dyLower = this.ActualHeight - this.ActualHeight * yLowerValue.GetRatio(minYValue, maxYValue);

                                this.cachedPoints[i].Add(new Point(dx, dy));
                                this.cachedUpperPoints[i].Add(new Point(dx, dyUpper));
                                this.cachedLowerPoints[i].Add(new Point(dx, dyLower));
                            }

                            StreamGeometry lineGeometry = new StreamGeometry();
                            using (StreamGeometryContext drawingContext = lineGeometry.Open())
                            {
                                for (int j = 0; j < this.cachedPoints[i].Count; ++j)
                                {
                                    if (j == 0)
                                    {
                                        drawingContext.BeginFigure(this.cachedPoints[i][j], false, false);
                                    }
                                    else
                                    {
                                        drawingContext.LineTo(this.cachedPoints[i][j], true, false);
                                    }
                                }
                            }

                            StreamGeometry areaGeometry = new StreamGeometry();
                            using (StreamGeometryContext drawingContext = areaGeometry.Open())
                            {
                                for (int j = 0; j < this.cachedUpperPoints[i].Count; ++j)
                                {
                                    if (j == 0)
                                    {
                                        drawingContext.BeginFigure(this.cachedUpperPoints[i][j], true, true);
                                    }
                                    else
                                    {
                                        drawingContext.LineTo(this.cachedUpperPoints[i][j], true, false);
                                    }
                                }

                                for (int j = this.cachedLowerPoints[i].Count - 1; j >= 0; --j)
                                {
                                    drawingContext.LineTo(this.cachedLowerPoints[i][j], true, false);
                                }
                            }

                            lineGeometry.Freeze();
                            areaGeometry.Freeze();

                            dc.DrawGeometry(null, this.Chart.Series[i].Pen, lineGeometry);
                            dc.DrawGeometry(this.Chart.Series[i].RangeBrush, this.Chart.Series[i].RangePen, areaGeometry);
                        }
                    }
                }
            }

            dc.Pop();

            base.OnRender(dc);
        }

        internal override void HintSeriesChanged()
        {
            this.cachedPoints = null;
            this.cachedUpperPoints = null;
            this.cachedLowerPoints = null;

            InvalidateVisual();

            base.HintSeriesChanged();
        }

        internal override void HintSeriesChanged(int series, int index)
        {
            if (series < this.cachedNoSeries)
            {
                int relativeIndex = index - this.cachedStartIndex[series];

                int count = this.cachedPoints[series].Count - relativeIndex;

                if (count > 0)
                {
                    this.cachedPoints[series].RemoveRange(relativeIndex, count);
                }

                count = this.cachedUpperPoints[series].Count - relativeIndex;

                if (count > 0)
                {
                    this.cachedUpperPoints[series].RemoveRange(relativeIndex, count);
                }

                count = this.cachedLowerPoints[series].Count - relativeIndex;

                if (count > 0)
                {
                    this.cachedLowerPoints[series].RemoveRange(relativeIndex, count);
                }
            }

            InvalidateVisual();

            base.HintSeriesChanged(series, index);
        }
    }
}
